package com.ews.onlineexchange.service;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;

import com.ews.onlineexchange.model.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

public abstract class EntityQuery<T extends DataEntity> implements Serializable {	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	public static final int MAX_RESULTS_FEW = 10;
	public static final int MAX_RESULTS_NORMAL = 15;
	public static final int MAX_RESULTS_LOT = 20;
	
	Integer firstResult;
	Integer maxResults = MAX_RESULTS_NORMAL;
	String order;
	String orderBy;
	String dir;
	
	Long resultCount;
	Map<String, Object> parameterValues = new HashMap<String, Object>();
	
	List<T> resultList;
	String region;
	@Inject
	protected EntityManager entityManager;

    public List<T> getResultList() {
//    	long start = System.currentTimeMillis();
//    	System.out.println(getClass());
    	if (resultList == null)
    		init();
//    	
//    	long end = System.currentTimeMillis();
//    	System.out.println("用时："+(end - start));
    	return truncResultList(resultList);
    }

	public Long getResultCount() {
		if (resultCount == null)
			init();
		return resultCount;
	}

	public void setResultCount(Long resultCount) {
		this.resultCount = resultCount;
	}
    
    private void init() {
    	initResultList();
		initResultCount();
    }
    
    private void initResultList() {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteria = cb.createQuery(getClassType());        
        Root<T> entity = criteria.from(getClassType());
        Order[] orderArray = getOrderArray(cb, entity);
        if ( orderArray != null && orderArray.length > 0 )
        	criteria.select(entity).orderBy(orderArray);
        List<Predicate> restrictions = getRestrictions(criteria, cb, entity);
		criteria.where(restrictions.toArray(new Predicate[restrictions.size()]));
		if (isDistinct())
			criteria.distinct(true);
		fetch(entity);
        TypedQuery<T> query = entityManager.createQuery(criteria);        
        setHint( query );
        if (firstResult != null)
        	query.setFirstResult(getFirstResult());
        if (maxResults != null)
        	query.setMaxResults(getMaxResults() + 1);
		setResultList(query.getResultList());    
    }
    
    protected void setHint(TypedQuery<T> query) {
    	
    }

	protected boolean isDistinct() {
		return false;
	}

	protected void fetch(Root<T> entity) {}

	protected List<Predicate> getRestrictions(CriteriaBuilder cb, Root<T> entity){
    	return new ArrayList<Predicate>();
    }
    
    protected List<Predicate> getRestrictions(CriteriaQuery cq, CriteriaBuilder cb, Root<T> entity){
    	return getRestrictions(cb, entity);
    }

	private void initResultCount() {	
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<Long> countCriteria = cb.createQuery(Long.class);
		Root<T> entity = countCriteria.from(getClassType());
		if (isDistinct())
			countCriteria.select(cb.countDistinct(entity));
		else 
			countCriteria.select(cb.count(entity));
		List<Predicate> restrictions = getRestrictions(countCriteria, cb, entity);
		countCriteria.where(restrictions.toArray(new Predicate[restrictions.size()]));
		setResultCount(entityManager.createQuery(countCriteria).getSingleResult());
    }

	private Class<T> getClassType() {
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();        
        return (Class<T>) parameterizedType.getActualTypeArguments()[0];
    }
    
    protected Order[] getOrderArray(CriteriaBuilder cb, Root<T> entity) {
    	Order[] defaultOrderArray = new Order[2];
		defaultOrderArray[0] = cb.desc( entity.get( "createDate" ) );
		defaultOrderArray[1] = cb.desc( entity.get( "id" ) );
    	if ( StringUtils.isBlank( getOrderBy() ) ) {
    		if (getOrder() == null || getOrder().length() == 0) {
        		return defaultOrderArray;
        	}
    		
        	String[] strOrderArray = getOrder().split(",");
        	Order[] orderArray = new Order[strOrderArray.length];
        	for (int i = 0; i < strOrderArray.length; i++) {    		
    			String orderString = strOrderArray[i].trim();
    			String fieldName = getFieldName(orderString);
    			if (orderString.toLowerCase().contains("desc")) {
    				orderArray[i] = cb.desc(entity.get(fieldName));
    			} else {
    				orderArray[i] = cb.asc(entity.get(fieldName));
    			}
    		}
        	return orderArray;
    	} else {
    		Order[] orderArray = new Order[1];
        	
    		if (getDir().toLowerCase().contains("desc")) {
    			orderArray[0] = cb.desc( entity.get( getOrderBy() ) );
    		} else {
    			orderArray[0] = cb.asc( entity.get( getOrderBy() ) );
    		}
    		
    		return ArrayUtils.addAll( orderArray, defaultOrderArray );
    	}
    }

	private String getFieldName(String orderString) {
		orderString = replaceAll(orderString, " ASC", "");
		orderString = replaceAll(orderString, " DESC", "");
		orderString = orderString.trim();
		return orderString;
	}
	
	public String replaceAll(String input, String regex, String replacement) {  
        Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);  
        Matcher m = p.matcher(input);  
        String result = m.replaceAll(replacement);  
        return result;  
    }  
	
	public void refresh() {
		resultCount = null;
	    resultList = null;
	}
		
	public boolean isNextExists() {
		getResultList();
		return resultList!=null && getMaxResults()!=null &&
	             resultList.size() > getMaxResults();
	}
	
	public boolean isPreviousExists() {
		return getFirstResult() != null && getFirstResult() != 0;
	}
	
	public boolean isPaginated() {
		return isNextExists() || isPreviousExists();
	}

	public Long getLastFirstResult() {
		Integer pc = getPageCount();
		return pc == null ? null : (pc.longValue() - 1) * getMaxResults();
	}

	/**
	 * Get the index of the first result of the next page
	 * 
	 */
	public int getNextFirstResult() {
		Integer fr = getFirstResult();
		return (fr == null ? 0 : fr) + getMaxResults();
	}

	/**
	 * Get the index of the first result of the previous page
	 * 
	 */
	public int getPreviousFirstResult() {
		Integer fr = getFirstResult();
		Integer mr = getMaxResults();
		return mr >= (fr == null ? 0 : fr) ? 0 : fr - mr;
	}
   
	/**
	 * Get the total number of pages
	 * 
	 */
	public Integer getPageCount() {
		if (getMaxResults() == null) {
			return null;
		} else {
			int rc = getResultCount().intValue();
			int mr = getMaxResults().intValue();
			int pages = rc / mr;
			return rc % mr == 0 ? pages : pages + 1;
		}
	}

	protected List<T> truncResultList(List<T> results) {
		Integer mr = getMaxResults();
		if (mr != null && results.size() > mr) {
			return results.subList(0, mr);
		} else {
			return results;
		}
	}

	public Integer getFirstResult() {
		return firstResult;
	}

	public void setFirstResult(Integer firstResult) {
		this.firstResult = firstResult;
	}

	public Integer getMaxResults() {
		return maxResults;
	}

	public void setMaxResults(Integer maxResults) {
		this.maxResults = maxResults;
	}

	public String getOrder() {
		return order;
	}

	public void setOrder(String order) {
		this.order = order;
	}

	public void setResultList(List<T> resultList) {
		this.resultList = resultList;
	}
	
	public Map<String, Object> getParameterValues() {
		return parameterValues;
	}
	
	public static String getStringRestriction(Object value) {
		if (value == null)
			return getStringRestriction("");
		return getStringRestriction(value.toString());
	}

	public static String getStringRestriction(String value) {
		if (value == null)
			value = "";
		return "%" + value + "%";
	}

	public String getOrderBy() {
		return orderBy;
	}

	public void setOrderBy( String orderBy ) {
		this.orderBy = orderBy;
	}

	public String getDir() {
		return dir;
	}

	public void setDir( String dir ) {
		this.dir = dir;
	}
	public String getRegion() {
		return region;
	}

	public void setRegion(String region) {
		this.region = region;
	}

	@Inject
	UserService userService;
	private final static String ALWAYS_FALSE_REGION = "00xx00xx00xx";

	public void addBulletinRegionRestriction(List<Predicate> restrictions, Join<Bulletin, ServiceBuyer> buyer,CriteriaBuilder cb){
		User user = userService.getLoginedUser();
		if(user != null){
			String userRegion = user.getRegion();
			if(!"370501".equals(userRegion)){
				//当不是市辖区用户时，啥也看不到
				restrictions.add(cb.equal(buyer.get("region"), ALWAYS_FALSE_REGION));
			}

		}
	}

	/**
	 * 市里可以查看所有的
	 * @param restrictions
	 * @param buyer
	 * @param cb
	 */
	public void addBulletinRegionRestriction_view(List<Predicate> restrictions, Join<Bulletin, ServiceBuyer> buyer,CriteriaBuilder cb){
		User user = userService.getLoginedUser();
		if(user != null){
			String userRegion = user.getRegion();
			if(!"370501".equals(userRegion)){
				restrictions.add(cb.equal(buyer.get("region"), user.getRegion()));
			}
		}
	}
	public void addCertRegionRestriction(List<Predicate> restrictions, Join<QualificationCert, ServiceSupplier> supplier, CriteriaBuilder cb){
		User user = userService.getLoginedUser();
		if(user != null){
			String userRegion = user.getRegion();
			if(!"370501".equals(userRegion)){
				//当不是市辖区用户时，啥也看不到
				restrictions.add(cb.equal(supplier.get("region"), ALWAYS_FALSE_REGION));
			}
//			if("370501".equals(userRegion))restrictions.add(cb.or(cb.equal(supplier.get("region"), user.getRegion()),cb.equal(supplier.get("region"),"000000")));
//			else restrictions.add(cb.equal(supplier.get("region"), user.getRegion()));
		}
	}

//	/**
//	 * 审核权限-市里管理员用户  资质、公告审核
//	 * @param restrictions
//	 * @param supplier
//	 * @param cb
//	 */
//	public void addAuditRegionRestriction(List<Predicate> restrictions, Join<QualificationCert, ServiceSupplier> supplier, CriteriaBuilder cb){
//		User user = userService.getLoginedUser();
//		if(user != null){
//			String userRegion = user.getRegion();
//			if("370501".equals(userRegion))restrictions.add(cb.or(cb.equal(supplier.get("region"), user.getRegion()),cb.equal(supplier.get("region"),"000000")));
//			else restrictions.add(cb.equal(supplier.get("region"), user.getRegion()));
//		}
//	}


}
